package com.marketlive.system.encryption;

import java.security.KeyFactory;
import java.security.PublicKey;

/*
(C) Copyright MarketLive. 2006. All rights reserved.
MarketLive is a trademark of MarketLive, Inc.
Warning: This computer program is protected by copyright law and international treaties.
Unauthorized reproduction or distribution of this program, or any portion of it, may result
in severe civil and criminal penalties, and will be prosecuted to the maximum extent
possible under the law.
*/

import java.security.interfaces.RSAPublicKey;
import java.security.spec.EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.util.HashMap;

import javax.crypto.Cipher;

import org.apache.commons.codec.EncoderException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.marketlive.system.encryption.EncryptionException;
import org.marketlive.system.encryption.IEncryptor;
import org.marketlive.system.encryption.IKeyStore;
import org.marketlive.system.encryption.IKeyStoreManager;

/**
 * Implementation of the Bouncy Castle (BC) RSA Encryption/Decryption algorithm.
 * Used to encrypt Credit Card Numbers and passwords.
 * 
 */
public class BCRSAEncryptor implements IEncryptor {
	
    private static Log log = LogFactory.getLog(BCRSAEncryptor.class);
    private static final String ALGORITHM = "RSA";
    private static final String PROVIDER = "BC";   
        
    
    /** HashMap to hold the public keys, referenced by key_code. */
    private HashMap publicKeyMap = new HashMap();

    /** Reference to the KeyStore Manager. */
    private IKeyStoreManager keyStoreManager;  

    /** Injects the KeyStore Manager reference into this class. */
    public void setKeyStoreManager(IKeyStoreManager keyStoreManager) {
        this.keyStoreManager = keyStoreManager;
    }   

    /**
     * Init method to setup encryption/decryption.
     */
    public void init() {
    	if (log.isDebugEnabled()) {
    		log.debug("init called");
    	}
        String currentCode = null;
        IKeyStore keyStore = null;
        RSAPublicKey publicKey = null;
        
        currentCode = keyStoreManager.getKeyCode();
        keyStore = keyStoreManager.findByCode(currentCode);        
        
        if (keyStore == null) {
            throw new RuntimeException("Error loading keystore from BCRSA Encryptor.");
        }
        try {
            publicKey = (RSAPublicKey) getPublicKeyFromString(keyStore.getPublicKey());             
            
        } catch (Exception e) {
            throw new RuntimeException("Error reading public key. Verify that your version of Bouncy Castle matches the version used for key creation.", e);
        }
        publicKeyMap.put(currentCode, publicKey);        
    }

    /**
     * Encrypt the input string using BC RSA encryption and return the encrypted string.
     * @param inString the string to encrypt.
     * @return returns and encrypted string.
     */
    public String encrypt(String inString) throws EncryptionException {
    	String keyCode = keyStoreManager.getKeyCode();
        return encrypt(keyCode, inString);
    }

    /**
     * Encrypt the input string using BC RSA encryption and return the encrypted string.
     * @param keyCode the keyCode to lookup the private key from the KeyStore table.
     * @param inString the string to encrypt.
     * @return returns and encrypted credit card number string.
     */

    public String encrypt(String keyCode, String inString) throws EncryptionException  {
        String encodedString = "";

        RSAPublicKey publicKey = (RSAPublicKey) publicKeyMap.get(keyCode);
        String currentCode = null;
        IKeyStore keyStore = null;
        
        if (publicKey == null) {        	
        	
        	if (keyCode == null) {
        		currentCode = keyStoreManager.getKeyCode();
        		keyStore = keyStoreManager.findByCode(currentCode);
        	} else {
        		keyStore = keyStoreManager.findByCode(keyCode);
        	}
             
            if (keyStore == null) {
                throw new RuntimeException("Error loading keystore from BCRSA Encryptor.");
            }
            try {
            	publicKey = (RSAPublicKey) getPublicKeyFromString(keyStore.getPublicKey());            	
            	
            } catch (Exception e) {
                throw new RuntimeException("Error reading public key. Verify that your version of Bouncy Castle matches the version used for key creation.", e);
            }
            publicKeyMap.put(currentCode, publicKey);
        }
        byte[] encryptedData = encryptRSA(inString, publicKey);    	       
        try {
        	encodedString = keyStoreManager.encodeBase64(encryptedData);
        } catch (EncoderException ee) {
        	log.error("Error encrypting string");
        }

        return encodedString;
    }
    
    /**
     * Generates Public Key from Base64 encoded string
     * @param inString Base64 encoded string which represents the key
     * @return The PublicKey
     * @throws java.lang.Exception
     */
    private PublicKey getPublicKeyFromString(String inString) throws Exception {
    	if (null == inString) {
    		return null;
    	}
    	
    	byte [] aByte = keyStoreManager.decodeBase64(inString);
    	if (null != aByte) {
	        KeyFactory keyFactory = KeyFactory.getInstance(ALGORITHM);
	        EncodedKeySpec publicKeySpec = new X509EncodedKeySpec(aByte);
	        PublicKey publicKey = keyFactory.generatePublic(publicKeySpec);
	        return publicKey;
    	} else {
    		return null;
    	}
    }    

    /**
     * Encrypts a string using the implemented encrytion scheme and returns encrypted string.
     * @param unencryptedString  the string to encrypt.
     * @return  returns an encrypted string.
     */
    private final byte[] encryptRSA(final String unencryptedString, RSAPublicKey publicKey) {
        try {
            Cipher encoder = Cipher.getInstance("RSA/ECB/PKCS1Padding", PROVIDER);
            encoder.init(Cipher.ENCRYPT_MODE, publicKey);
            byte [] ab = keyStoreManager.makeBlank(unencryptedString).getBytes("UTF-8");
            return encoder.doFinal(ab);
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("Error Encrypting Credit Card Number", e);
        }
    }

}
